from PyQt6.QtWidgets import (QApplication, QMainWindow, QPushButton, 
                           QVBoxLayout, QWidget, QMessageBox,QLabel,QTextBrowser)
from PyQt6.QtCore import Qt, QTimer
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
import sys
import os
import time
import threading
from random import random   
from selenium.webdriver.common.action_chains import ActionChains  # 添加这行导入
from random import random, uniform, randint, choice
class MainWindow(QMainWindow):
    def human_type(self, element, text):  # 修改方法定义，添加 self 参数
        """模拟人工输入"""
        try:
            # 先清空输入框
            element.clear()
            time.sleep(self.heartsleep *random())
            
            # 逐字输入
            for char in text:
                element.send_keys(char)
                # 每个字符之间的延迟在 0.1-0.3 秒之间
            time.sleep(self.heartsleep * 0.1 *random())
            
            # 输入完成后的随机等待
            time.sleep(self.heartsleep  *random())
            
        except Exception as e:
            print(f"输入文字失败: {str(e)}")
    
    def random_sleep(self, min_seconds=0, max_seconds=1):
        """
        随机等待一段时间
        
        Args:
            min_seconds (float): 最小等待时间（秒）
            max_seconds (float): 最大等待时间（秒）
        
        Returns:
            float: 实际等待的时间
        """
        try:
            # 生成随机等待时间
            sleep_time = uniform(min_seconds, max_seconds)
            
            # 实际等待
            time.sleep(sleep_time)
            
            # 可选：记录等待信息
            print(f"等待 {sleep_time:.2f} 秒...")
            
            return sleep_time
            
        except Exception as e:
            print(f"随机等待出错: {str(e)}")
            # 如果出错，使用最小等待时间
            time.sleep(min_seconds)
            return min_seconds
    def human_move_and_click(self, element):
        """模拟人工移动鼠标并点击"""
        try:
            actions = ActionChains(self.driver)
            
            # 随机选择一个偏移量
            offset_x = randint(-5, 5)
            offset_y = randint(-5, 5)
            
            # 移动到元素附近
            actions.move_to_element_with_offset(element, offset_x, offset_y)
            actions.move_to_element(element)
            time.sleep(self.heartsleep *random())
            
            # 点击
            actions.click()
            actions.perform()
            time.sleep(self.heartsleep* 2 *random())
            # 点击后的随机等待
            
        except Exception as e:
            print(f"点击操作失败: {str(e)}")   
    def human_move_and_move(self, element):
        """模拟人工移动鼠标并点击"""
        try:
            actions = ActionChains(self.driver)
            
            # 随机选择一个偏移量
            offset_x = randint(-5, 5)
            offset_y = randint(-5, 5)
            
            # 移动到元素附近
            actions.move_to_element_with_offset(element, offset_x, offset_y)
            actions.move_to_element(element)
            time.sleep(self.heartsleep *random())
            
            # 点击
            # actions.click()
            actions.perform()
            time.sleep(self.heartsleep* 2 *random())
            # 点击后的随机等待
            
        except Exception as e:
            print(f"点击操作失败: {str(e)}")   
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.driver = None
        self.current_job_index = 0
        self.jobs = []
        
        # 正常人类操作需要的时间间隔

    def init_ui(self):
        self.setWindowTitle('BOSS直聘自动求职')
        self.setGeometry(100, 100, 600, 400)
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        # time.sleep(2 * random())  # random() 返回 0-1 之间的浮点数
        self.heartsleep=float(self.read_jobs_config(file_path='./jobs.txt',code='heartsleep')[0])
        # 心跳等待时间
        self.mansleep=float(self.read_jobs_config(file_path='./jobs.txt',code='mansleep')[0])
        self.startpage=int(self.read_jobs_config(file_path='./jobs.txt',code='startpage')[0])
        self.endpage=int(self.read_jobs_config(file_path='./jobs.txt',code='endpage')[0])

        start_button = QPushButton('开始', self)
        start_button.clicked.connect(self.start_process)
        test_button = QPushButton('test', self)
        test_button.clicked.connect(self.test_process)

        self.tip_text = QTextBrowser(self)
        self.tip_text.setReadOnly(True)  # 设置为只读
      # 设置大小和位置
        self.tip_text.setFixedHeight(150)  # 设置固定高度
        self.tip_text.setMinimumWidth(400)  # 设置最小宽度
        
        # 设置滚动条策略
        self.tip_text.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)  # 总是显示垂直滚动条
        self.tip_text.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)  # 总是显示水平滚动条
        
        # 设置文本换行模式
        self.tip_text.setLineWrapMode(QTextBrowser.LineWrapMode.NoWrap)  # 不自动换行
        self.tip_text.setAlignment(Qt.AlignmentFlag.AlignCenter)  # 居中对齐
        self.tip_text.setTextInteractionFlags(
            Qt.TextInteractionFlag.TextSelectableByMouse |  # 允许鼠标选择
            Qt.TextInteractionFlag.TextSelectableByKeyboard  # 允许键盘选择
        )
        
        # 设置样式
        self.tip_text.setStyleSheet("""
            QTextBrowser {
                background-color: #ffffff;
                border: 1px solid #cccccc;
                border-radius: 4px;
                padding: 5px;
            }
            QScrollBar:vertical {
                width: 12px;
                background: #f0f0f0;
            }
            QScrollBar:horizontal {
                height: 12px;
                background: #f0f0f0;
            }
            QScrollBar::handle:vertical,
            QScrollBar::handle:horizontal {
                background: #cdcdcd;
                border-radius: 6px;
            }
            QScrollBar::handle:vertical:hover,
            QScrollBar::handle:horizontal:hover {
                background: #a8a8a8;
            }
        """)
        layout.addWidget(start_button)
        layout.addWidget(test_button)

        layout.addWidget(self.tip_text)
        
    def read_jobs_config(self,file_path='./jobs.txt' ,code='jobs' ):
        """
        读取配置文件，解析多个 jobs=value 格式的内容
        
        Args:
            file_path (str): 文件路径，默认为 'jobs.txt'
            
        Returns:
            list: jobs值的列表，如 ['crawl', 'python']
            
        Example:
            jobs.txt 内容:
            jobs=crawl
            jobs=python
        """
        jobs_list = []
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                for line in file:
                    # 去除空白字符
                    line = line.strip()
                    if line and '=' in line:
                        key, value = line.split('=', 1)
                        key = key.strip()
                        value = value.strip()
                        if key == code:
                            jobs_list.append(value)
                            
            return jobs_list
            
        except FileNotFoundError:
            print(f"文件 {file_path} 不存在")
            self.tip_text.setText(f"文件 {file_path} 不存在")
            return []
        except Exception as e:
            print(f"读取文件时发生错误: {e}")
            return []
    def random_sleep(self,min_sec=0.5, max_sec=2):
            """随机等待"""
            time.sleep(uniform(min_sec, max_sec))
    def human_type(self,element, text):
            """模拟人工输入"""
            # 先清空输入框
            element.clear()
            self.random_sleep(0.5, 1)
            
            # 逐字输入，带随机延迟
            for char in text:
                element.send_keys(char)
                # 每个字符之间的延迟在 0.1-0.3 秒之间
                self.random_sleep(0.1, 0.3)
            
            # 输入完成后的随机等待
            self.random_sleep(0.5, 1.5)
    def human_scroll_to_top(self):
        """模拟人工滚动到顶部"""
        try:
            # 获取当前滚动位置
            current_position = self.driver.execute_script("return window.pageYOffset;")
            
            while current_position > 0:
                # 随机滚动距离（向上200-400像素）
                scroll_distance = randint(200, 400)
                current_position = max(0, current_position - scroll_distance)
                
                # 平滑滚动
                self.driver.execute_script(
                    f"window.scrollTo({{top: {current_position}, behavior: 'smooth'}});"
                )
                
                # 随机等待
                time.sleep(self.heartsleep *random())
            
            self.tip_text.append("已滚动到页面顶部")
            
        except Exception as e:
            self.tip_text.append(f"滚动过程出错: {str(e)}")
    def human_scroll_down(self):
            """模拟人工滚动"""
            try:
                # 获取页面高度
                total_height = self.driver.execute_script("return document.body.scrollHeight")
                current_height = 0
                
                while current_height < total_height:
                    # 随机滚动距离
                    scroll_distance = randint(100, 400)
                    current_height += scroll_distance
                    
                    # 平滑滚动
                    self.driver.execute_script(f"window.scrollTo({{top: {current_height}, behavior: 'smooth'}})")
                    
                    # 随机暂停
                    time.sleep(self.heartsleep *random())
                    
            except Exception as e:
                print(f"滚动出错: {str(e)}")    
    def test_process(self): 
        joblist=self.read_jobs_config(file_path='./jobs.txt',code='jobs')
        chatcontent=self.read_jobs_config(file_path='./jobs.txt',code='chatcontent')
        self.search_job(joblist,chatcontent)         

    def start_process(self):

        try:
            # 检查jobs.txt是否存在
            curpath=os.getcwd()
            file_path=f"{curpath}/jobs.txt"
            if not os.path.exists('./jobs.txt'):
                self.tip_text.setText(f"文件 {file_path} 不存在")
                return
            joblist=self.read_jobs_config(file_path,'jobs')
            chatcontent=self.read_jobs_config(file_path='jobs.txt',code='chatcontent')

            if not joblist:
                # QMessageBox.warning(self, '错误', 'jobs.txt文件为空！')
                self.tip_text.setText( '错误', 'jobs.txt文件为空！')

                return
            user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
            headers = {
                'authority':'www.zhipin.com',
                'method':'GET',
                'path':'/web/geek/job?query=%E7%88%AC%E8%99%AB%E5%B7%A5%E7%A8%8B%E5%B8%88&city=101280600',
                'scheme':'https',
                'accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
                'accept-encoding':'gzip, deflate, br, zstd',
                'accept-language':'zh-CN,zh;q=0.9',
                'cache-control':'max-age=0'
            }
            
            # 初始化浏览器
            if not self.driver:
                chrome_options = webdriver.ChromeOptions()
                user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
                chrome_options.add_argument(f'--user-agent={user_agent}')
                chrome_options.add_argument('--ignore-certificate-errors')
                chrome_options.add_argument('--ignore-ssl-errors')
                chrome_options.add_argument('--disable-extensions')  # 禁用扩展
                chrome_options.add_argument('--disable-software-rasterizer')  # 禁用软件光栅化
     # 添加常规选项
                chrome_options.add_argument('--disable-logging')  # 禁用日志
                chrome_options.add_argument('--log-level=3')  # 仅显示严重错误
                chrome_options.add_argument('--silent')  # 静默模式
                
                # 禁用开发者工具相关的警告
                chrome_options.add_experimental_option('excludeSwitches', [
                    'enable-logging',
                    'enable-automation'
                ])
                
                # 设置首选项
                prefs = {
                    'devtools.preferences.currentDockState': '"undocked"',  # 开发者工具设置
                    'devtools.timeline.hidden-categories': '["*"]',  # 隐藏所有类别
                    'profile.default_content_setting_values.notifications': 2,  # 禁用通知
                    'profile.default_content_settings.popups': 0  # 禁用弹窗
                }
                chrome_options.add_experimental_option('prefs', prefs)
                self.driver = webdriver.Chrome(options=chrome_options)
                # self.driver.maximize_window()

            # 打开BOSS直聘
            self.driver.get('https://www.zhipin.com')
            cookie_string='lastCity=101280600; ab_guid=55bb756b-f453-4d82-803f-f088c2755b01; __g=-; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1734657565; HMACCOUNT=101A4EFC2A1784E9; wt2=DH3HsTcL4iaWe2bwm6xsiUvuiZCYH0sriy8OMIr-Qowu99vKYOBz64mA7wr7IaKru9KC9HlQyPDH_aB1ZgpWZYw~~; wbg=0; zp_at=wbanFvXyMNowZPlt5yb9PsnkVrwBFz-0R43gy4qZ58c~; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1734658490; bst=V2RNwnEuP92VpvVtRuzBgcKCm07DrQwSQ~|RNwnEuP92VpvVtRuzBgcKCm07DrWwCU~; __c=1734657564; __a=92956196.1730690547.1730690547.1734657564.28.2.15.28; __zp_stoken__=9662fS03DrsOBw4PDjEA8BhIaHx82O0hNJyxDSzlPQk9LTU1EQUtNRSJLO8OZw43DgsSObG%2FDhidIJk1NSUhLT01CRRZNQcWAw4tKTDLDusOIw4vEg2pow5scPcONZMOvw4gaT8OCB8SAwrYaYMOIOTs%2Fw4NLSE42GcK3w4jDj07DhcOLw4HCu8OLw6zDhEg2cxjDiDFKBgYVH0o2XlptGFhqW2NuVB9SUV84QURKTUPCkD5NHR0SGBsaGhUHHBoaFQYdGxsUBh0GBhEbGDVNwqnDi1LCncSRwrnDvMSqwpfCq8KXwqbCpFLClGLDjMK0wobDhMKhw4LCs8K9wrxowqNowqvCvcK7bFzCtnVRwpbCjFdrwrlXw4tewrlzwrcVZWZiVR4QEhkeTB5PVsOH'
            cookie_list = cookie_string.split(';')
            
            # 处理每个cookie
            for cookie in cookie_list:
                if '=' in cookie:
                    name, value = cookie.strip().split('=', 1)
                    cookie_dict = {
                        'name': name.strip(),
                        'value': value.strip(),
                        'domain': '.zhipin.com'  # 设置domain
                    }
                    try:
                        self.driver.add_cookie(cookie_dict)
                    except Exception as e:
                        print(f"添加cookie失败 {name}: {e}")
            # if not self.set_cookies_from_string(cookie_string):
            #     print("Cookie设置失败")
            #     return False
            time.sleep(self.mansleep* 2 *random())  
                # 验证cookies是否设置成功
            all_cookies = self.driver.get_cookies()
            print(f"当前cookies数量: {len(all_cookies)}")
            # print(all_cookies)
            self.driver.refresh()
           
            for job in joblist:
                print(f'配置: {job}')
            print(f'聊天内容: {chatcontent}')

        except Exception as e:
            # QMessageBox.warning(self, '错误', f'发生错误: {str(e)}')
            self.tip_text.setText(f'发生错误:: {str(e)}')
        time.sleep(self.mansleep * 2 * random())
    def start_url_monitor(self):
        # 创建定时器监控URL变化
        self.url_timer = QTimer()
        self.url_timer.timeout.connect(self.check_url)
        self.url_timer.start(500)  # 每秒检查一次

    def check_url(self):
        try:
            current_url = self.driver.current_url
            if 'www.zhipin.com/web/geek/job' in current_url:
                self.url_timer.stop()
        except Exception as e:
            print(f"URL检查错误: {e}")

    def search_job(self,jobs,chatcontent):
        self.heartsleep=float(self.read_jobs_config(file_path='./jobs.txt',code='heartsleep')[0])
        # 心跳等待时间
        self.mansleep=float(self.read_jobs_config(file_path='./jobs.txt',code='mansleep')[0])
        self.startpage=int(self.read_jobs_config(file_path='./jobs.txt',code='startpage')[0])
        self.endpage=int(self.read_jobs_config(file_path='./jobs.txt',code='endpage')[0])
        try:
            # 等待搜索框出现
            
            for index ,job in enumerate(jobs):
                if index>0:
                    self.human_scroll_to_top()
                search_input=self.driver.find_element(By.XPATH,"//*[@id='wrap']/div[3]/div/div[1]/div[1]/form/div[2]/p/input")
                time.sleep(self.heartsleep * 2 *random())
                self.human_move_and_move(search_input)
                search_input.clear()
                # search_input.send_keys(job)
                self.human_type(search_input,job)
                time.sleep(self.mansleep* 2 *random())

                search_input.send_keys(Keys.RETURN)
                time.sleep(self.mansleep* 2 *random())
                jobslistelements=None
                original_handle=self.driver.current_window_handle
                try:
                        # 等待元素存在、可见且可点击
                        jobslistelements = WebDriverWait(self.driver, 30).until(
                            
                                  EC.element_to_be_clickable((By.CSS_SELECTOR, 'a.job-card-left')))
                        
                        jobslistelements=self.driver.find_elements(By.CSS_SELECTOR, 'a.job-card-left')
                        print(f"成功找到 {len(jobslistelements)} 个可交互的职位")
                        
                except Exception as e:
                    print(f"等待职位列表超时（30秒）: {e}")
                # jobslistelements=self.driver.find_elements(By.CSS_SELECTOR, 'a.job-card-left')
                url1=self.driver.current_url
                for page in range(self.startpage,self.endpage):
                    if page>0:
                        urlpage=url1+ f'&page={page}'
                        self.driver.get(urlpage)
                        self.human_scroll_down()
                        # pagepath=f'//*[@id="wrap"]/div[2]/div[2]/div/div[1]/div[2]/div/div/div/a[{page+2}]'
                        # pageelement=self.driver.find_element(By.XPATH,pagepath)
                        # self.human_move_and_move(pageelement)
                        # pageelement.click()
                        time.sleep(self.heartsleep* 2 *random())
                        self.human_scroll_to_top()
                        jobslistelements = WebDriverWait(self.driver, 30).until(
                            
                                  EC.element_to_be_clickable((By.CSS_SELECTOR, 'a.job-card-left')))
                        
                        jobslistelements=self.driver.find_elements(By.CSS_SELECTOR, 'a.job-card-left')
                        print(f"成功找到 {len(jobslistelements)} 个可交互的职位")
                        # self.driver.get(newurl)
                    for index,jobelement in enumerate(jobslistelements):
                        # if index>2:
                        #     break
                        print(page,'（页）->（行数）',index+1,f'找到职位: {jobelement.text}')
                        self.human_move_and_click(jobelement)
                        original_window = self.driver.current_window_handle
                        href = jobelement.get_attribute('href')

                        # jobelement.click()
                        self.random_sleep(1, 2)  # 等待新窗口打开
                        WebDriverWait(self.driver, 10).until(
                                                        lambda d: len(d.window_handles) > 1
                                                    )
                        time.sleep(self.heartsleep * 2 *random())
                        for window_handle in self.driver.window_handles:
                                if window_handle != original_window:
                                    self.driver.switch_to.window(window_handle)
                                    break
                        sencond_window=''
                        original_handles = self.driver.window_handles

                        try:
                            chat_button = WebDriverWait(self.driver, 10).until(
                                EC.presence_of_element_located((By.XPATH, '//*[@id="main"]/div[1]/div/div/div[1]/div[3]/div[1]/div'))
                            )
                            sencond_window=self.driver.current_window_handle
                            self.human_move_and_click(chat_button)

                            # chat_button.click()
                        except Exception as e:
                            print(f"立即沟通失败: {e}")
                            print(f"找立即沟通失败: {e}") 
                        # 又弹出新窗口
                        # 
                        new_window = None
                        newtext=self.driver.page_source
                        if '工作经历不匹配' in newtext  :
                            self.driver.back()
                            self.driver.close()
                            self.driver.switch_to.window(original_window)
                            continue
                        if   '已向BOSS发送消息'  in newtext:    
                            continuepath=['/html/body/div[11]/div[2]/div[3]/div/span[2]','//*[@id="chat-input"]','/html/body/div[11]/div[2]/div[3]/div/span[2]']
                            for cpath in continuepath:
                                try:
                                    continuesendelemnet=WebDriverWait(self.driver, 10).until(
                                    EC.presence_of_element_located((By.XPATH, cpath)))
                                    self.human_move_and_click(continuesendelemnet)
                                    # continuesendelemnet.click()
                                except Exception as e:
                                    print(f"继续发送失败: {e}")
                        url3=self.driver.current_url    
                        chatxpathlist=['/html/body/div[11]/div[2]/div[2]/div/div[1]/div[2]/textarea','//*[@id="chat-input"]']
                        for xp in chatxpathlist:
                            try:
                                message_input = WebDriverWait(self.driver, 10).until(
                                    EC.presence_of_element_located((By.XPATH, xp))
                                )
                                self.human_move_and_move(message_input)
                                # message_input.send_keys(chatcontent[0])
                                self.human_type(message_input,chatcontent[0])
                                print('发送：'+chatcontent[0])
                                self.human_move_and_move(message_input)
                                message_input.send_keys(Keys.RETURN)
                                time.sleep(self.heartsleep* 2 *random())
                                url4=self.driver.current_url
                                if 'www.zhipin.com/web/geek/job' not  in url4:
                                    self.driver.close()

                                    self.driver.switch_to.window(original_window)   
                                    break
                               
                            except Exception as e:
                                print(f"发信息失败: {e}")
                            # 回退到找工作界面
                                # print(f"窗口操作失败: {str(e)}")
                        url4=self.driver.current_url
                        while 'www.zhipin.com/web/geek/job' not  in url4:
                            self.driver.close()
                            self.driver.switch_to.window(original_window)
                            url4=self.driver.current_url

                            #         break
                            #     else:
                            #         self.driver.close()
                            #         current_url=self.driver.current_url
                                

             
            print( '完成，所有职位都已处理完毕！')
            
        except Exception as e:
            print(f'搜索职位时发生错误: {str(e)}')


    def closeEvent(self, event):
        if self.driver:
            self.driver.quit()
        event.accept()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())